'use strict';

const fs = require('fs');
const path = require('path');
const fse = require('fs-extra');
const ejs = require('ejs');
const glob = require('glob');
const semver = require('semver');
const userHome = require('user-home');
const inquirer = require('inquirer');
const Command = require('@gok-cli/command');
const Package = require('@gok-cli/package');
const log = require('@gok-cli/log');
const { spinnerStart, sleep, execAsync } = require('@gok-cli/utils');

const getProjectTemp = require('./getProjectTemp');

const TYPE_PROJECT = 'project';
const TYPE_COMPONENT = 'component';
const TEMP_TYPE_NORMAL = 'normal'; // 标准模板
const TEMP_TYPE_CUSTOM = 'custom'; // 自定义模板
const WHITE_COMMAND = ['npm', 'cnpm', 'yarn']; // 白名单命令

class InitCommand extends Command {
    init() {
        this.projectName = this._argv[0] || ''; // 包含project Name, --force
        this.force = !!this._argv[1].force;
        log.verbose('projectName', this.projectName);
        log.verbose('force', this.force);
    }

    async exec() {
        try {
            const projectInfo = await this.prepare();
            if (projectInfo) {
                this.projectInfo = projectInfo;
                log.verbose('projectInfo', projectInfo)
                await this.downLoadTemp();
                await this.installTemp();
            }
        } catch (e) {
            log.error(e.message);
            if (process.env.LOG_LEVEL === 'verbose') {
                console.log(e);
            }
        }
    }

    // 检查命令
    checkCommand(command) {
        if (WHITE_COMMAND.includes(command)) {
            return command
        }
        return null;
    }

    async execCommand(command, errMsg) {
        let ret;
        if (command) {
            const cmdArry = command.split(' ');
            const cmd = this.checkCommand(cmdArry[0]);
            if (!cmd) {
                throw new Error('命令不存在！命令：' + command)
            }
            const args = cmdArry.slice(1);
            ret = await execAsync(cmd, args, {
                stdio: 'inherit',
                cwd: process.cwd(),
            })
            if (ret !== 0) {
                throw new Error(errMsg)
            }
            return ret;
        }
    }
    // ejs
    async ejsRender({ ignore }) {
        const dir = process.cwd();
        const projectInfo = this.projectInfo;
        return new Promise((resolve, reject) => {
            glob('**', {
                cwd: dir,
                ignore: ignore,
                nodir: true,
            }, (err, files) => {
                if (err) {
                    reject(err);
                }
                Promise.all(files.map(file => {
                    const filePath = path.join(dir, file);
                    return new Promise((resolve1, reject1) => {
                        ejs.renderFile(filePath, projectInfo, {}, (err, result) => {
                            if (err) {
                                reject1(err);
                            } else {
                                fse.writeFileSync(filePath, result);
                                resolve1(result);
                            }
                        })
                    })
                })).then(() => {
                    resolve();
                }).catch(err => {
                    reject(err);
                })
            })
        })
    }

    // 安装模板
    async installTemp() {
        if (this.tempInfo) {
            if (!this.tempInfo.type) {
                this.tempInfo.type = TEMP_TYPE_NORMAL;
            }
            if (this.tempInfo.type === TEMP_TYPE_NORMAL) {
                await this.installNormalTemp();
            } else if (this.tempInfo.type === TEMP_TYPE_CUSTOM) {
                await this.installCustomTemp();
            } else {
                throw new Error('无法识别项目类型！');
            }
        } else {
            throw new Error('项目模板信息不存在！');
        }
    }

    async installNormalTemp() {
        let spinner = spinnerStart('正在安装模板...');
        await sleep();
        try {
            const templatePath = path.resolve(this.tempNpm.cacheFilePath, 'template');
            const targetPath = process.cwd();
            fse.ensureDirSync(templatePath);
            fse.ensureDirSync(targetPath);
            fse.copySync(templatePath, targetPath);
        } catch (error) {
            throw error;
        } finally {
            spinner.stop(true);
            log.success('模板安装成功');
        }
        const tempIgnore = this.tempInfo.ignore || [];
        const ignore = ['**/node_modules/**', ...tempIgnore];
        await this.ejsRender({ ignore });
        // 依赖安装
        const { installCommand, startCommand } = this.tempInfo;
        //依赖安装
        await this.execCommand(installCommand, '依赖安装过程中失败！');
        await this.execCommand(startCommand, '启动过程中失败！');
    }

    async installCustomTemp() {
        console.log('自定义安装');
    }

    // 下载线上模板
    async downLoadTemp() {
        // 通过项目模板api获取项目模板信息
        // 通过egg.js搭建服务
        // 通过npm存储
        const { projectTemp } = this.projectInfo;
        const tempInfo = this.temp.find(item => item.npmName === projectTemp);
        const targetPath = path.resolve(userHome, '.gok-cli', 'template');
        const storeDir = path.resolve(userHome, '.gok-cli', 'template', 'node_modules');
        this.tempInfo = tempInfo;
        const { npmName, version } = tempInfo;
        const tempNpm = new Package({
            targetPath,
            storeDir,
            pkgName: npmName,
            pkgVersion: version
        })
        if (! await tempNpm.exists()) {
            const spinner = spinnerStart('正在下载模板...');
            await sleep();
            try {
                await tempNpm.install();
            } catch (error) {
                throw error;
            } finally {
                spinner.stop(true);
                if(await tempNpm.exists()) {
                    log.success('下载模板成功');
                    this.tempNpm = tempNpm;
                }
            }
        } else {
            const spinner = spinnerStart('正在更新模板...');
            await sleep();
            try {
                await tempNpm.update();
            } catch (error) {
                throw error;
            } finally {
                spinner.stop(true);
                if(await tempNpm.exists()) {
                    log.success('更新模板成功');
                    this.tempNpm = tempNpm;
                }
            }
        }
    }

    async prepare() {
        const localPath = process.cwd();
        const temp = await getProjectTemp();
        if (!temp || temp.length === 0) {
            throw new Error('项目模板不存在')
        }
        this.temp = temp;
        // 目录不为空，询问是否更新
        if (!this.isDirEmpty(localPath)) {
            let ifContinue = false;
            if(!this.force) {
                ifContinue = (await inquirer.prompt({
                    type: 'confirm',
                    name: 'ifContinue',
                    default: false,
                    message: '当前文件不为空，是否继续创建项目?'
                })).ifContinue;
                if (!ifContinue) {
                    return;
                }
            }
            if (ifContinue || this.force) {
                // 给用户做二次确认
                const { confirmDelete } = await inquirer.prompt({
                    type: 'confirm',
                    name: 'confirmDelete',
                    default: false,
                    message: '是否确认清空当前目录下的文件?'
                })
                // 清空当前目录
                if (confirmDelete) {
                    fse.emptyDirSync(localPath);
                }
            }
        }
        return this.getProjectInfo();
    }

    async getProjectInfo() {
        function isValidName(v) {
            return /^[a-zA-Z]+([-][a-zA-Z]+[a-zA-Z0-9]*|[_][a-zA-Z]+[a-zA-Z0-9]*|[a-zA-Z0-9])*$/.test(v)
        }
        let projectInfo = {};
        let isProjectNameValid = false;
        if (isValidName(this.projectName)) {
            isProjectNameValid = true;
            projectInfo.projectName = this.projectName;
        }
        // 1.选择创建项目或组件
        const { type } = await inquirer.prompt({
            type: 'list',
            name: 'type',
            message: '请选择初始化类型',
            default: TYPE_PROJECT,
            choices: [{
                name: '项目',
                value: TYPE_PROJECT
            },{
                name: '组件',
                value: TYPE_COMPONENT
            }]
        })
        log.verbose('type', type);
        this.temp = this.temp.filter(temp => temp.tag.includes(type));
        const title = type === TYPE_PROJECT ? '项目' : '组件';
        const projectNamePrompt = {
            type: 'input',
            name: 'projectName',
            message: `请输入${title}名称`,
            default: '',
            validate:function (v) {
                // 1. 输入的首字符必须为英文字符
                // 2. 输入的尾字符必须为英文或数字，不能为字符
                // 3. 字符仅允许"-_"
                const done = this.async();
                setTimeout(() => {
                    if (!isValidName(v)) {
                        done(`请输入合法的${title}名称`);
                        return;
                    }
                    done(null, true);
                }, 0)
            },
            filter: function(v) {
                return v
            }
        }
        const projectPromt = [];
        if (!isProjectNameValid) {
            projectPromt.push(projectNamePrompt);
        }
        projectPromt.push({
            type: 'input',
            name: 'projectVersion',
            message: `请输入${title}版本号`,
            default: '1.0.0',
            validate:function (v) {
                const done = this.async();
                setTimeout(() => {
                    if (!(!!semver.valid(v))) {
                        done('请输入合法的版本号');
                        return;
                    }
                    done(null, true);
                }, 0)
            },
            filter: function(v) {
                if (!!semver.valid(v)) {
                    return semver.valid(v);
                }
                return v
            }
        },{
            type: 'list',
            name: 'projectTemp',
            message: `请选择${title}模板`,
            choices: this.createTempChoice()
        })
        if (type === TYPE_PROJECT) {
            const project = await inquirer.prompt(projectPromt);
            projectInfo = {
                ...projectInfo,
                type,
                ...project,
            }
        } else if (type === TYPE_COMPONENT) {
            const descriptionPrompt = {
                type: 'input',
                name: 'componentDescription',
                message: '请输入组件描述信息',
                default: '',
                validate: function (v) {
                    const done = this.async();
                    setTimeout(() => {
                        if (!v) {
                            done('请输入组件描述信息');
                            return;
                        }
                        done(null, true);
                    }, 0)
                }
            }
            projectPromt.push(descriptionPrompt);
            // 获取组件基本信息
            const component = await inquirer.prompt(projectPromt);
            projectInfo = {
                ...projectInfo,
                type,
                ...component,
            }
        } 
        // 生产className
        if (projectInfo.projectName) {
            projectInfo.name = projectInfo.projectName;
            projectInfo.className = require('kebab-case')(projectInfo.projectName).replace(/^-/, '');
        }
        if (projectInfo.projectVersion) {
            projectInfo.version = projectInfo.projectVersion;
        }

        if (projectInfo.componentDescription) {
            projectInfo.description = projectInfo.componentDescription;
        }
        return projectInfo;
    }

    createTempChoice() {
        return this.temp.map(item => ({
            name: item.name,
            value: item.npmName
        }))
    }

    isDirEmpty(localPath) {
        // 1.判断目录是否为空
        let fileList = fs.readdirSync(localPath);
        fileList = fileList.filter(file => (
            !file.startsWith('.') && ['node_modules'].indexOf(file) < 0
        ));
        return !fileList || fileList.length <= 0;
    }
}

function init(argv) {
    return new InitCommand(argv);
}
module.exports = init
module.exports.InitCommand = InitCommand
